from fastapi import FastAPI, Query
from pydantic import BaseModel, BaseSettings
from neo4j import AsyncGraphDatabase
from nodes import Graph
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = ["*"]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class Neo4jConfig(BaseSettings):
    url: str
    user_name: str
    password: str

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"


class Item(BaseModel):
    name: str
    price: float
    is_offer: bool | None = None


# 启动链接
@app.on_event("startup")
async def startup_event():
    print("startup")
    neo4j_conf = Neo4jConfig()  # 获取 Neo4j 数据库的配置信息
    global neo4j_driver
    neo4j_driver = AsyncGraphDatabase.driver(
        neo4j_conf.url, auth=(neo4j_conf.user_name, neo4j_conf.password)
    )


# 关闭数据库连接
@app.on_event("shutdown")
async def shutdown_event():
    print("shutdown")
    neo4j_driver.close()


# 主页
@app.get("/")
async def root():
    return {"message": "Hello World"}


# 数据清洗
# 删除孤立节点、单向边和孤立边
async def dat_clean():
    rs=[]
    isolated_node_query = """MATCH (n) 
                            WHERE NOT (n)--() 
                            DELETE n"""
    isolated_rel_query = """MATCH ()-[r]-() 
                            WHERE NOT ()--(r)--() 
                            DELETE r"""
    oneDirect_rel_query = """MATCH (start)-[r]->(end) 
                            WHERE NOT (end)-[]->(start) 
                            DELETE r"""
    rs.append(await neo4j_driver.execute_query(isolated_node_query.stats()))
    rs.append(await neo4j_driver.execute_query(isolated_rel_query.stats()))
    rs.append(await neo4j_driver.execute_query(oneDirect_rel_query.stats()))
    return rs


# 库中所有标签统计
@app.post("/label_and_count")
async def label_and_count():
    result = await neo4j_driver.execute_query(
        "MATCH (n) RETURN DISTINCT labels(n) AS nodeType, COUNT(n) AS nodeCount"
    )
    rs = [
        {
            "nodeType": record["nodeType"][0],
             "nodeCount": record["nodeCount"]
        }
        for record in result[0]
    ]
    return rs


# 库中所有关系统计
@app.post("/rel_and_count")
async def rel_and_count():
    result = await neo4j_driver.execute_query(
        "MATCH ()-[r]->() RETURN DISTINCT type(r) AS edgeType, COUNT(r) AS edgeCount"
    )
    rs = [
        {
            "edgeType": record["edgeType"],
             "edgeCount": record["edgeCount"]
        }
        for record in result[0]
    ]
    return rs


#todo 前后端沟通添加界面
# 针对所有编织物进行查询，对每种编织物被引用次数进行统计并生成统计量
async def article_that_cite():
    query = "MATCH (n:`分类`) RETURN COUNT{(n)-[:collocation]->()}AS citations "
    result = await neo4j_driver.execute_query(query).to_data_frame()
    rs=result.describe([0.25, 0.5, 0.75, 0.99])
    return rs


#todo 前后端沟通添加界面
# 针对所有编织物进行查询，对每种编织物引用次数进行统计并生成统计量
async def citations_analysis():
    query = "MATCH (n:`分类`) RETURN COUNT{(n)<-[:collocation]-()}AS citations "
    result = await neo4j_driver.execute_query(query).to_data_frame()
    rs=result.describe([0.25, 0.5, 0.75, 0.99])
    return rs


# 根据编织物名进行查找，查找与之搭配的纺织物并统计其相搭配物品数量
@app.post("/get_Braid_info")
async def get_Braid_info(author_name):
    query = (
        """ MATCH (n:`分类` {name: '%s'})<-[:collocation]-(`分类`) 
        RETURN `分类`.name AS name, `分类`.property AS property, `分类`.sid AS sid, COUNT { (`分类`)-[:collocation]-() } AS count 
        ORDER BY count DESC 
        LIMIT 20"""
        % author_name
    )
    parameters = {"authorName": author_name}
    result = await neo4j_driver.execute_query(query, parameters)
    rs = [
        {
            "name": record["name"],
            "property": record["property"],
            "sid": record["sid"],
            "count": record["count"],
        }
        for record in result[0]
    ]
    return rs


# 根据编织物名进行查找，查找与之搭配的纺织物的搭配纺织物得出潜在搭配对象
@app.post("/Braid_recom")
async def Braid_recom(author_name):
    query = """
    MATCH (n:`分类` {name: '%s'})<-[:collocation]-(`分类`)-[:collocation]-(coauthor)
    RETURN  coauthor.name AS name ,coauthor.property AS property ,coauthor.sid AS sid,COUNT{(n:`分类` {name:'%s'})<-[:collocation]-(`分类`)-[:collocation]-(coauthor)} AS count
    ORDER BY count DESC
    LIMIT 20
    """ % (
        author_name,
        author_name,
    )
    print(query)
    result = await neo4j_driver.execute_query(query)
    rs = [
        {
            "name": record["name"],
            "property": record["property"],
            "sid": record["sid"],
            "count": record["count"],
        }
        for record in result[0]
    ]
    return rs


@app.post("/Braid_style")
# 根据编织物名进行查找其可选设计样式
async def Braid_style(author_name):
    query = """
    MATCH (n:`分类` {name:$authorName})-[:property]-(`设计细节`)
    RETURN `设计细节`.name AS name , `设计细节`.sid AS sid
    LIMIT 20
    """
    parameters = {"authorName": author_name}
    result = await neo4j_driver.execute_query(query, parameters)
    rs = [
        {
            "name": record["name"], 
            "sid": record["sid"]
            }
         for record in result[0]
       ]
    return rs


# 根据节点分类查找结点  返回一个包含节点标签的字符串列表并去重
@app.post("/get_all_node_type")
async def get_node_type() -> list[str]:
    result = await neo4j_driver.execute_query(
        "MATCH (n) RETURN DISTINCT labels(n) AS labels"
    )
    rs = [record["labels"][0] for record in result[0]]
    return rs


# 根据边查找出所有包含此边的关系节点并去重
@app.post("/get_all_relationship_type")
async def get_relationship_type() -> list[str]:
    result = await neo4j_driver.execute_query(
        "MATCH ()-[r]->() RETURN DISTINCT type(r) AS r"
    )
    rs = [record["r"] for record in result[0]]
    return rs


# 获取节点分类名称传给前端 返回结果数量为1-25
# 接收节点分类名和返回结果的限制数量作为输入，根据节点类型查询匹配的节点信息，并返回节点名称和属性的列表,用字典names封装


@app.post("/query_nodes")
async def query_nodes(
    node_name: str = Query(
        max_length=50,
        min_length=2,
        regex=r"^[a-zA-z_\u4e00-\u9fa5]+[\w\u4e00-\u9fa5]+$",
        description="节点类型名",
    ),
    limit: int = Query(25, ge=1),
):
    result, _, _ = await neo4j_driver.execute_query(
        f"MATCH (n:{node_name}) RETURN n LIMIT {limit}"
    )
    names = [
        {"name": record["n"]["name"], 
        "property": record["n"]["property"]
        }
        for record in result
    ]
    return {"names": names}


# 获取边分类名称传给前端  返回结果数量限制为1-25
@app.post("/link_by_name", response_model=Graph)
async def link_by_name(
    name: str = Query(
        max_length=50, min_length=2, regex=r"^[a-zA-z_]{1,}[\w]+$", description="边名"
    ),
    limit: int = Query(25, ge=1),
):
    result, _, _ = await neo4j_driver.execute_query(
        f"MATCH p=()-[:{name}]->() RETURN p LIMIT {limit}"
    )
    map_category = {}
    set_node = set()
    nodes = []
    categories = []
    idx = 0
    links = []
    for record in result:
        link = {
            "source": record["p"].start_node.element_id,
            "target": record["p"].end_node.element_id,
        }
        links.append(link)
        for node in record["p"].nodes:
            label = list(node.labels)[0]
            if label not in map_category:
                categories.append({"name": label})
                map_category[label] = idx
                idx += 1
            node_dict = {
                "id": node.element_id,
                "name": node["name"],
                "value": node["property"],
                "category": map_category[label],
            }
            if node.element_id not in set_node:
                nodes.append(node_dict)
                set_node.add(node.element_id)
    return {"nodes": nodes, "links": links, "categories": categories}


# ====定义查询某一标签所包含的所有节点ID的接口
@app.post("/search_all_id_by_name")
async def search_all_id_by_name(
    node_name: str = Query(
        regex=r"^[a-zA-z_\u4e00-\u9fa5]+[\w\u4e00-\u9fa5]+$",   
        # 正则表达式regex限制节点名的格式，确保它只包含字母、下划线和汉字，并且以字母或汉字开头，可以包含数字和汉字，但不能以特殊字符结尾。
        description="输入标签名",
    ), 
):
    result= await neo4j_driver.execute_query(
        f"MATCH (n:{node_name}) RETURN n.sid "
    )
    print(result)
    if result:  
        node_Id = result
        return {"该标签包含的所有节点ID为": node_Id}
    else:
        return {"message": "Node not found"}   
      
 
# ====定义通过某一节点名字查询所有与该节点有关系的节点的接口 
@app.post("/search_names_by_name", response_model=Graph)
async def search_names_by_name(
    name: str  = Query(
        description="输入节点名字",),
):
    result, _, _ =await neo4j_driver.execute_query(f"MATCH p =(n where n.name=\"{name}\")-[]-() RETURN p")
    map_category = {}
    set_node = set()
    nodes = []
    categories = []
    idx = 0
    links = []
    for record in result:
        link = {
            "source": record["p"].start_node.element_id,
            "target": record["p"].end_node.element_id,
        }
        links.append(link)
        for node in record["p"].nodes:
            label = list(node.labels)[0]
            if label not in map_category:
                categories.append({"name": label})
                map_category[label] = idx
                idx += 1
            node_dict = {
                "id": node.element_id,
                "name": node["name"],
                "value": node["property"],
                "category": map_category[label],
            }
            if node.element_id not in set_node:
                nodes.append(node_dict)
                set_node.add(node.element_id)
    return {"nodes": nodes, "links": links, "categories": categories}
